package core.utils;


import core.Model;
import maps.utils.Coordinate;

import java.util.Iterator;
import java.util.Map.Entry;


/**
 * Wrapper-class for all attributes associated with a data-point in the log.
 */
public class LogData implements Comparable<LogData>{
    private final int signalId;
    private final double signalValue;
    private final long timeInMillis;
    private final Coordinate coordinates;
    private final boolean warning;
    private final Variable variable;
    private int cachedHashCode = Integer.MIN_VALUE;

    /**
     * Constructs new LogData object. Note that all fields are final.
     * @param signalId The id of the signal that has generated the log.
     * @param signalValue The value of the signal at time of logging.
     * @param timeInMillis The time at which the data was logged. Given in standard Unix-time.
     * @param x X-coordinate of the vehicles position at the time of logging.
     * @param y Y-coordinate of the vehicles position at the time of logging.
     */
    public LogData(int signalId, double signalValue, long timeInMillis, double x, double y, boolean warning){
        this.signalId = signalId;
        this.signalValue = signalValue;
        this.timeInMillis = timeInMillis;
        coordinates = new Coordinate(x, y);
        this.warning = warning;
        this.variable = findVariable(signalId);

        if (variable == null){
            throw new IllegalArgumentException("Illegal signalID. Must correspond to a valid variable");
        }

    }

    public Variable getVariable(){
        return variable;
    }

    @Override
    public int compareTo(LogData o) {
        return (int)(timeInMillis - o.timeInMillis);
    }

    public int getSignalId() {
        return signalId;
    }

    public double getSignalValue() {
        return signalValue;
    }

    public int getTimeInMillis() {
        return (int)timeInMillis;
    }

    public Coordinate getCoord() {
        return coordinates;
    }

    public boolean getWarning() {
        return warning;
    }

    private static Variable findVariable(int id){
        Variable res = null;

        Iterator<Entry<Variable,Integer>> it = Model.getVariableIds().entrySet().iterator();

        Entry<Variable,Integer> currEntry;
        while (it.hasNext()){
            currEntry = it.next();
            if (currEntry.getValue() == id){
                res = currEntry.getKey();
                break;
            }
        }

        return res;
    }

    @Override
    public int hashCode() {
        if (cachedHashCode == Integer.MIN_VALUE){
            int res = 17;
            res = 31 * res + signalId;
            long valueLong = Double.doubleToLongBits(signalValue);
            int valueHash = (int) (valueLong ^ (valueLong >>> 32));
            res = 31 * res + valueHash;
            int timeHash = (int) (timeInMillis ^ (timeInMillis >>> 32));
            res = 31 * res + timeHash;
            res = 31 * res + coordinates.hashCode();
            int warnHash = (warning ? 1 : 0);
            res = 31 * res + warnHash;
            cachedHashCode = res;
        }
        return cachedHashCode;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof LogData){
            LogData log = (LogData) obj;
            return (signalId == log.signalId &&
                    signalValue == log.signalValue &&
                    timeInMillis == log.timeInMillis &&
                    this.coordinates.equals(((LogData) obj).coordinates) &&
                    warning == log.warning);
        }
        else {
            return false;
        }
    }

    //XXX - This implementation is fairly slow, should be optimized sometime.
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("signalID: " + signalId + "\n");
        sb.append("signalValue: " + signalValue + "\n");
        sb.append("timInMillis: " + timeInMillis + "\n");
        sb.append("xCoord: " + coordinates.getX() + "\n");
        sb.append("yCoord: " + coordinates.getY() + "\n");
        sb.append("warning: " + warning);
        return sb.toString();
    }
}
